﻿namespace EasyToolkit.ModelDefinition
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using EasyToolkit.ModelDefinition.Definition.XmlDefinition;
    using EasyToolkit.Tools.Extensions;
    using EasyToolkit.Tools.Log;

    public sealed class ModelDefinitionManager : IModelDefinitionManager
    {
        #region properties and attributes
        #region singleton
        private static readonly Lazy<IModelDefinitionManager> instance = new Lazy<IModelDefinitionManager>(() => Activator.CreateInstance(typeof(ModelDefinitionManager), true) as IModelDefinitionManager);

        /// <summary>Return the 'ModelDefinitionManager' singleton instance</summary>
        public static IModelDefinitionManager Instance { get { return instance.Value; } }
        #endregion

        private readonly object syncObj = new object(); 

        private Dictionary<string, List<IModelAccessor>> modelAccessorByName
        { get; set; }

        private Dictionary<string, IModelType> modelTypeByName
        { get; set; }
        #endregion

        #region .ctors
        private ModelDefinitionManager()
        {
            modelAccessorByName = new Dictionary<string, List<IModelAccessor>>();
            modelTypeByName = new Dictionary<string, IModelType>();
        }
        #endregion

        #region public methods
        /// <summary> Implements <see cref="IModelDefinitionManager.AddConfigurationFromFile"/> </summary> 
        public void AddConfigurationFromFile(string configurationFilePath)
        {
            try
            {
                Logger.Instance.LogFormat(LogType.Info, "Model configuration file '{0}' integration starting ...", configurationFilePath.EmptyIfNull());

                XmlModelConfiguration xmlModelConfiguration = XmlModelConfiguration.CreateInstanceFromFile(configurationFilePath);
                if (xmlModelConfiguration != null)
                    PopulateFromXmlModelConfiguration(xmlModelConfiguration);
            }
            catch (Exception ex)
            {
                throw new EasyToolkitException(string.Format("Retrieve model configuration from file '{0}' failed: {1}", configurationFilePath.EmptyIfNull(), ex.Message), ex);
            }
            finally
            {
                Logger.Instance.LogFormat(LogType.Info, "Model configuration file '{0}' integration finished", configurationFilePath.EmptyIfNull());
            }
        }

        /// <summary> Implements <see cref="IModelDefinitionManager.AddConfigurationFromXml"/> </summary> 
        public void AddConfigurationFromXml(string xmlString)
        {
            string xmlForMessage = xmlString.EmptyIfNull();
            xmlForMessage = xmlForMessage.Length > 75 ? xmlForMessage.Substring(0, 75) + "..." : xmlForMessage;

            try
            {
                Logger.Instance.LogFormat(LogType.Info, "Integration of model configuration xml '{0}' starting...", xmlForMessage);
                XmlModelConfiguration xmlModelConfiguration = XmlModelConfiguration.CreateInstanceFromXml(xmlString);
                if (xmlModelConfiguration != null)
                    PopulateFromXmlModelConfiguration(xmlModelConfiguration);
            }
            catch (Exception ex)
            {
                throw new EasyToolkitException(string.Format("Retrieve model configuration from xml '{0}' failed: {1}", xmlForMessage, ex.Message), ex);
            }
            finally
            {
                Logger.Instance.LogFormat(LogType.Info, "Integration of model configuration xml '{0}' finished", xmlForMessage);
            }
        }

        /// <summary> Implements <see cref="IModelDefinitionManager.GetAccessors"/> </summary> 
        public IEnumerable<IModelAccessor> GetAccessors()
        {
            lock (syncObj)
            {
                List<IModelAccessor> ret = null;
                if (modelAccessorByName != null)
                    ret = modelAccessorByName.Values.SelectMany(a => a).OrderBy(a => a.Name).ToList();
                return ret;
            }
        }

        /// <summary> Implements <see cref="IModelDefinitionManager.GetAccessors"/> </summary> 
        public IEnumerable<IModelAccessor> GetAccessor(string name)
        {
            lock (syncObj)
            {
                List<IModelAccessor> ret = null;
                if (modelAccessorByName != null)
                    modelAccessorByName.TryGetValue(name, out ret);
                return ret;
            }
        }

        /// <summary> Implements <see cref="IModelDefinitionManager.GetModelDataTypes"/> </summary> 
        public IEnumerable<IModelType> GetModelDataTypes()
        {
            lock (syncObj)
            {
                return modelTypeByName.Values.OrderBy(a => a.Name).ToList();
            }
        }

        /// <summary> Implements <see cref="IModelDefinitionManager.GetModelType"/> </summary> 
        public IModelType GetModelType(string name)
        {
            lock (syncObj)
            {
                IModelType ret = null;
                modelTypeByName.TryGetValue(name, out ret);
                return ret;
            }
        }

        /// <summary> Implements <see cref="IModelDefinitionManager.GetModelType"/> </summary> 
        public void ResolveModel()
        {
            try
            {
                Logger.Instance.Log(LogType.Info, "Starting the model resolution...");

                bool onError = false;
                if (modelTypeByName != null)
                {
                    foreach (IModelType type in modelTypeByName.Values)
                    {
                        try
                        {
                            (type as ModelType).ResolveDependencies();
                        }
                        catch 
                        { onError = true; }
                    }
                }

                if (modelAccessorByName != null)
                {
                    try
                    {
                        modelAccessorByName.SelectMany(a => a.Value)
                                           .ToList()
                                           .ForEach(a => (a as ModelAccessor).ResolveDependencies());
                    }
                    catch
                    { onError = true; }
                }

                if (onError)
                    throw new EasyToolkitException("Error(s) found");
            }
            catch (Exception ex)
            {
                throw new EasyToolkitException(string.Format("Model resolution failed: {0}. Please check the log.", ex.Message));
            }
            finally
            {
                Logger.Instance.Log(LogType.Info, "Model resolution finished."); 
            }
        }
        #endregion

        #region private methods
        private void PopulateFromXmlModelConfiguration(XmlModelConfiguration xmlModelConfiguration)
        {
            lock (syncObj)
            {
                if (xmlModelConfiguration.TypeDefinitions != null)
                {
                    foreach (XmlModelTypeDefinition typeDefinition in xmlModelConfiguration.TypeDefinitions)
                    {
                        ModelType modelType = new ModelType(typeDefinition);
                        if (modelType != null)
                        {
                            if (modelTypeByName.ContainsKey(modelType.Name))
                                Logger.Instance.LogFormat(LogType.Warn, "The model type '{0}' is overriden.", modelType.Name.EmptyIfNull());
                            modelTypeByName[modelType.Name] = modelType;
                        }
                    }
                }

                if (xmlModelConfiguration.ModelAccessorDefinitions != null)
                {
                    foreach (XmlModelAccessorDefinition modelAccessorDefinition in xmlModelConfiguration.ModelAccessorDefinitions)
                    {
                        ModelAccessor accessor = new ModelAccessor(modelAccessorDefinition);
                        if (accessor != null)
                        {
                            List<IModelAccessor> accessorList = null;
                            if (!modelAccessorByName.TryGetValue(accessor.Name, out accessorList))
                            {
                                accessorList = new List<IModelAccessor>();
                                modelAccessorByName[accessor.Name] = accessorList;
                            }
                            accessorList.Add(accessor);
                        }
                    }
                }
            }
        }
        #endregion
    }
}
